﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Reflection;

namespace Cjc.Data.Mapping
{
	public abstract class TranslatingEnumerableQuery
	{
		public QueryContextBase Context { get; private set; }

		protected TranslatingEnumerableQuery( QueryContextBase context )
		{
			this.Context = context;
		}

		public static IQueryable Create( QueryContextBase context, Type elementType, IEnumerable sequence )
		{
			return (IQueryable)Activator.CreateInstance(
				typeof( TranslatingEnumerableQuery<> ).MakeGenericType( new Type[] { elementType } ),
				BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
				null,
				new object[] { context, sequence },
				null );
		}

		public static IQueryable Create( QueryContextBase context, Type elementType, Expression expression )
		{
			return (IQueryable)Activator.CreateInstance(
				typeof( TranslatingEnumerableQuery<> ).MakeGenericType( new Type[] { elementType } ),
				BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
				null,
				new object[] { context, expression },
				null );
		}

		public abstract IEnumerable Enumerable { get; }
		public abstract Expression Expression { get; }

		public override string ToString()
		{
			return ( Expression != null ) ? Expression.ToString() : base.ToString();
		}
	}

	public class TranslatingEnumerableQuery<T> : TranslatingEnumerableQuery, IOrderedQueryable<T>, IQueryProvider
	{
		private static Expression source = Expression.Parameter( typeof( IQueryable<T> ), typeof( T ).Name );

		private Expression expression;
		private LambdaExpression translated;
		private LambdaExpression translatedCount;
		private IEnumerable<T> enumerable;

		public TranslatingEnumerableQuery( QueryContextBase context )
			: this( context, null )
		{
		}

		public TranslatingEnumerableQuery( QueryContextBase context, Expression expression )
			: base( context )
		{
			this.expression = expression ?? source;
		}

		private LambdaExpression Translated()
		{
			if ( translated == null && Context != null && expression != null ) translated = Context.TranslateToLambda( expression );
			return translated;
		}

		private LambdaExpression TranslatedCount()
		{
			if ( translatedCount == null && Translated() != null )
			{
				translatedCount = Expression.Lambda(
					Expression.Call(
						typeof( Enumerable ),
						"Count",
						new[] { typeof( T ) },
						translated.Body ),
					translated.Parameters.ToArray() );
			}

			return translatedCount;
		}

		public IEnumerable<T> GetEnumerable()
		{
			if ( enumerable == null && Context != null ) enumerable = Context.GetEnumerable<T>( Translated() );
			return enumerable;
		}

		public override IEnumerable Enumerable { get { return GetEnumerable(); } }
		public override Expression Expression { get { return expression; } }
/*
		public int Count()
		{
			return ( Context != null )
				? ( (IQueryProvider)new EnumerableQuery<T>( Context.BindToContext( TranslatedCount() ) ) ).Execute<int>()
				: 0;
		}
		*/
		#region IEnumerable<T> Members

		public IEnumerator<T> GetEnumerator() { return GetEnumerable().GetEnumerator(); }

		#endregion

		#region IEnumerable Members

		IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

		#endregion

		#region IQueryable Members

		Type IQueryable.ElementType { get { return typeof( T ); } }
		Expression IQueryable.Expression { get { return expression; } }
		IQueryProvider IQueryable.Provider { get { return this; } }

		#endregion

		#region IQueryProvider Members

		public IQueryable<S> CreateQuery<S>( Expression expression )
		{
			return new TranslatingEnumerableQuery<S>( Context, expression );
		}

		public IQueryable CreateQuery( Expression expression )
		{
			return TranslatingEnumerableQuery.Create( Context, typeof( T ), expression );
		}

		public TResult Execute<TResult>( Expression expression )
		{
			if ( Context == null ) return default( TResult );

			return ( this.expression == expression )
				? Context.Execute<TResult>( Translated() )
				: Context.Execute<TResult>( Context.TranslateToLambda( expression ) );
		}

		public object Execute( Expression expression )
		{
			return Execute<object>( expression );
		}

		#endregion
	}
}